use crate::error::{Error, Result};
use crate::logic::common::cache::ICacheService;
use futures::future::BoxFuture;
use log::error;
use redis::aio::Connection;
use redis::{Client, RedisResult};
use std::time::Duration;

#[derive(Debug, Clone)]
pub struct RedisService {
    pub client: Client,
}

impl RedisService {
    pub fn new(url: &str) -> Self {
        let client = Client::open(url).expect("redis connect failed!");
        Self { client }
    }

    pub async fn get_conn(&self) -> Result<Connection> {
        let conn = self.client.get_async_connection().await;
        if conn.is_err() {
            let err = format!("RedisService connect fail:{}", conn.err().unwrap());
            error!("{}", err);
            return Err(crate::error::Error::from(err));
        }
        return Ok(conn.unwrap());
    }
}

impl ICacheService for RedisService {
    fn set_str(&self, key: &str, value: &str) -> BoxFuture<Result<String>> {
        let key = key.to_string();
        let value = value.to_string();
        Box::pin(async move {
            return self.set_str_exp(&key, &value, None).await;
        })
    }

    fn get_str(&self, key: &str) -> BoxFuture<Result<String>> {
        let key = key.to_string();
        Box::pin(async move {
            let mut conn = self.get_conn().await?;
            let result: RedisResult<Option<String>> =
                redis::cmd("GET").arg(&[&key]).query_async(&mut conn).await;
            return match result {
                Ok(v) => Ok(v.unwrap_or_default()),
                Err(e) => Err(Error::from(format!(
                    "RedisService get_string({}) fail:{}",
                    key,
                    e.to_string()
                ))),
            };
        })
    }

    fn set_str_exp(
        &self,
        key: &str,
        value: &str,
        exp: Option<Duration>,
    ) -> BoxFuture<Result<String>> {
        let key = key.to_string();
        let value = value.to_string();
        Box::pin(async move {
            let mut conn = self.get_conn().await?;
            return if exp.is_none() {
                match redis::cmd("SET")
                    .arg(&[key, value])
                    .query_async(&mut conn)
                    .await
                {
                    Ok(v) => Ok(v),
                    Err(e) => Err(Error::from(format!(
                        "RedisService set_string_ex fail:{}",
                        e.to_string()
                    ))),
                }
            } else {
                match redis::cmd("SET")
                    .arg(&[&key, &value, "EX", &exp.unwrap().as_secs().to_string()])
                    .query_async(&mut conn)
                    .await
                {
                    Ok(v) => Ok(v),
                    Err(e) => Err(Error::from(format!(
                        "RedisService set_string_ex fail:{}",
                        e.to_string()
                    ))),
                }
            };
        })
    }

    fn del_str(&self, key: &str) -> BoxFuture<Result<u8>> {
        let key = key.to_string();
        Box::pin(async move {
            let mut conn = self.get_conn().await?;
            match redis::cmd("DEL").arg(&[key]).query_async(&mut conn).await {
                Ok(v) => Ok(v),
                Err(e) => Err(Error::from(format!(
                    "RedisService del_str fail:{}",
                    e.to_string()
                ))),
            }
        })
    }

    fn ttl(&self, key: &str) -> BoxFuture<Result<i64>> {
        let key = key.to_string();
        Box::pin(async move {
            let mut conn = self.get_conn().await?;
            return match redis::cmd("TTL").arg(&[key]).query_async(&mut conn).await {
                Ok(v) => Ok(v),
                Err(e) => Err(Error::from(format!(
                    "RedisService ttl fail:{}",
                    e.to_string()
                ))),
            };
        })
    }
}
